﻿#region File Description
//-----------------------------------------------------------------------------
// Level.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;
using System.IO;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Input;
using System.IO.IsolatedStorage;

namespace OutliersSample
{
    /// <summary>
    /// A uniform grid of tiles with collections of gems and enemies.
    /// The level owns the player and controls the game's win and lose
    /// conditions as well as scoring.
    /// </summary>
    class Level : IDisposable
    {
        // Physical structure of the level.
        private Tile[,] tiles;
        //private Texture2D[] layers;
        private Layer[] layers;
        // The layer which entities are drawn on top of.
        private const int EntityLayer = 2;

        // Entities in the level.
        public Player Player
        {
            get { return player; }
        }
        Player player;

        private List<Gem> gems = new List<Gem>();
        public List<Enemy> enemies = new List<Enemy>();

        // 3/29/2012 - moving platforms
        public List<MovableTile> movableTiles = new List<MovableTile>();

        // Key locations in the level.        
        private Vector2 start;
        //for checkpoints
        public Vector2 checkpoint;
        private Point exit = InvalidPosition;
        private static readonly Point InvalidPosition = new Point(-1, -1);

        // Level game state.
        private float cameraPosition;
        public float cameraPositionYAxis;
        private Random random = new Random(354668); // Arbitrary, but constant seed

        public int Score
        {
            get { return score; }
        }
        int score;

        public bool ReachedExit
        {
            get { return reachedExit; }
        }
        bool reachedExit;

        public TimeSpan TimeRemaining
        {
            get { return timeRemaining; }
        }
        TimeSpan timeRemaining;

        private const int PointsPerSecond = 5;

        // Level content.        
        public ContentManager Content
        {
            get { return content; }
        }
        ContentManager content;

        private SoundEffect exitReachedSound;
        // private SoundEffect checkpointReachedSound;

        #region Loading

        /// <summary>
        /// Constructs a new level.
        /// </summary>
        /// <param name="serviceProvider">
        /// The service provider that will be used to construct a ContentManager.
        /// </param>
        /// <param name="fileStream">
        /// A stream containing the tile data.
        /// </param>
        public Level(IServiceProvider serviceProvider, Stream fileStream, int levelIndex)
        {
            // Create a new content manager to load content used just by this level.
            content = new ContentManager(serviceProvider, "Content");

            timeRemaining = TimeSpan.FromMinutes(5.0);

            LoadTiles(fileStream);

            // Load background layer textures. For now, all levels must
            // use the same backgrounds and only use the left-most part of them.
            //////layers = new Texture2D[3];
            //////for (int i = 0; i < layers.Length; ++i)
            //////{
            //////    // Choose a random segment if each background layer for level variety.
            //////    int segmentIndex = levelIndex;
            //////    layers[i] = Content.Load<Texture2D>("Backgrounds/Layer" + i + "_" + segmentIndex);
            //////}

            // added - 3/22/2012 - 3:36 am
            // The new code initializes the array with three new Layer objects. Each of these objects loads a different texture, and has a different scrolling speed (the third parameter of the Layer constructor).
            layers = new Layer[3];
            //layers[0] = new Layer(Content, "Backgrounds/Layer0", 0.2f);
            //layers[1] = new Layer(Content, "Backgrounds/Layer1", 0.5f);
            //layers[2] = new Layer(Content, "Backgrounds/Layer2", 0.8f);


//////////0 - Scene 1 - Dream 
//////////1 Level (green)
//////////2 Level (green)
//////////3 - Scene 2 - Internet
//////////4 Level green
//////////5 Level green
//////////6 - Scene 3 - Find Zander - Jungle
//////////7 Level green?
//////////8 Level green?
//////////9 - Scene 4 - Cora - New Zeeland
//////////10 Level (sand)
//////////11 Level (sand)
//////////12 Scene 5 - Tiberiuous - Nepal - Starts with the whistle/reed
//////////13 Level scotland
//////////14 Level scotland
//////////15 Scene 6 - Parker - Scotland - scotish bag pipes
//////////16 Level sand
//////////17 Level sand
//////////18 Scene 7 - Saul, Giza - sand
//////////19 Level
//////////20 Level 
//////////21 Scene 8 - Ian - Palace
//////////22 Level
//////////23 Level
//////////24 - Scene 9 - meeting zander
//////////25 Level
//////////26 Level
//////////27 - Scene 10 - robotics
//////////28 - Scene 11 - Success!

            switch (levelIndex)
            {
                case 0: // Scene 1
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 1: //City - Toyko
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 2: //City - Toyko
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 3: //City - Toyko
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 4: //City - Toyko
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 5: //Scene 2
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 6: //Jungle
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 7: //Jungle
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 8: //Jungle
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 9: //Jungle
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 10: //Scene 3 - Meeting Zander in Jail
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 11: // Need new level here
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 12:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 13:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 14:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 15:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 16:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 17:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 18:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 19:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 20:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 21:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 22:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 23:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 24:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 25:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 26:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Night1", 0.8f);
                        break;
                    }
                case 27:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Night1", 0.8f);
                        break;
                    }
                case 28: //last evel
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Night1", 0.8f);
                        break;
                    }
                case 29:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 30:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 31:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/BlueSky1", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Clouds1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Clouds2", 0.8f);
                        break;
                    }
                case 32:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/Layer0", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Layer1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Layer2", 0.8f);
                        break;
                    }
                default:
                    {
                        layers[0] = new Layer(Content, "Backgrounds/Layer0", 0.2f);
                        layers[1] = new Layer(Content, "Backgrounds/Layer1", 0.5f);
                        layers[2] = new Layer(Content, "Backgrounds/Layer2", 0.8f);
                        break;
                    }
            }

            //
            // Load sounds.
            //

            //Exit
            exitReachedSound = Content.Load<SoundEffect>("Sounds/ExitReached");

            //Checkpoint
            // checkpointReachedSound = Content.Load<SoundEffect>("Sounds/CheckpointReached");
        }

        /// <summary>
        /// Iterates over every tile in the structure file and loads its
        /// appearance and behavior. This method also validates that the
        /// file is well-formed with a player start point, exit, etc.
        /// </summary>
        /// <param name="fileStream">
        /// A stream containing the tile data.
        /// </param>
        private void LoadTiles(Stream fileStream)
        {
            // Load the level and ensure all of the lines are the same length.
            int width;
            List<string> lines = new List<string>();
            using (StreamReader reader = new StreamReader(fileStream))
            {
                string line = reader.ReadLine();
                width = line.Length;
                while (line != null)
                {
                    lines.Add(line);
                    if (line.Length != width)
                        throw new Exception(String.Format("The length of line {0} is different from all preceeding lines.", lines.Count));
                    line = reader.ReadLine();
                }
            }

            // Allocate the tile grid.
            tiles = new Tile[width, lines.Count];

            // Loop over every tile position,
            for (int y = 0; y < Height; ++y)
            {
                for (int x = 0; x < Width; ++x)
                {
                    // to load each tile.
                    char tileType = lines[y][x];
                    tiles[x, y] = LoadTile(tileType, x, y);
                }
            }

            // Verify that the level has a beginning and an end.
            if (Player == null)
                throw new NotSupportedException("A level must have a starting point.");
            if (exit == InvalidPosition)
                throw new NotSupportedException("A level must have an exit.");

        }

        /// <summary>
        /// Loads an individual tile's appearance and behavior.
        /// </summary>
        /// <param name="tileType">
        /// The character loaded from the structure file which
        /// indicates what should be loaded.
        /// </param>
        /// <param name="x">
        /// The X location of this tile in tile space.
        /// </param>
        /// <param name="y">
        /// The Y location of this tile in tile space.
        /// </param>
        /// <returns>The loaded tile.</returns>
        private Tile LoadTile(char tileType, int x, int y)
        {
            switch (tileType)
            {
                // Blank space
                case '.':
                    return new Tile(null, TileCollision.Passable);

                //Checkpoint - A checkpoint tile can be added to levels using the 'P' character
                case 'P':
                    return LoadTile("Checkpoint", TileCollision.Checkpoint);

                // Exit
                case 'X':
                    return LoadExitTile(x, y);

                // Gem
                case 'G':
                    return LoadGemTile(x, y);

                // Floating platform
                case 'Z':
                    return LoadTile("ZanderTop", TileCollision.Impassable);

                // Floating platform
                case 'z':
                    return LoadTile("ZanderBottom", TileCollision.Impassable);

                // Floating platform
                case '-':
                    return LoadTile("Platform", TileCollision.Platform);

                // Moving platform - Horizontal
                case 'm':
                    return LoadMovableTile(x, y, TileCollision.Platform);

                // Various enemies
                case 'A':
                    return LoadEnemyTile(x, y, "MonsterA");
                case 'B':
                    return LoadEnemyTile(x, y, "MonsterB");
                case 'C':
                    return LoadEnemyTile(x, y, "MonsterC");
                case 'D':
                    return LoadEnemyTile(x, y, "MonsterD");

                // Platform block
                case '~':
                    return LoadVarietyTile("BlockB", 2, TileCollision.Platform);

                // Passable block
                case ':':
                    return LoadVarietyTile("BlockB", 2, TileCollision.Passable);

                // Player 1 start point
                case '1':
                    return LoadStartTile(x, y);

                // Impassable block
                case '#':
                    return LoadVarietyTile("BlockA", 7, TileCollision.Impassable);

                // Floating platform
                case '2':
                    return LoadTile("Bark1", TileCollision.Impassable);

                // Floating platform
                case '3':
                    return LoadTile("Bark2", TileCollision.Platform);

                // Floating platform
                case '4':
                    return LoadTile("Bark3", TileCollision.Impassable);

                // Floating platform
                case '5':
                    return LoadTile("Gold1", TileCollision.Impassable);

                // Floating platform
                case 'e':
                    return LoadTile("Leafy1", TileCollision.Impassable);

                // Floating platform
                case 'f':
                    return LoadTile("Leafy2", TileCollision.Impassable);

                // Floating platform
                case '8':
                    return LoadTile("Leafy3", TileCollision.Impassable);

                // Floating platform
                case 'h':
                    return LoadTile("Mud1", TileCollision.Impassable);

                // Floating platform
                case 'i':
                    return LoadTile("Mud2", TileCollision.Impassable);

                // Floating platform
                case 'j':
                    return LoadTile("Mud3", TileCollision.Impassable);

                // Floating platform
                case 'k':
                    return LoadTile("Pattern1", TileCollision.Impassable);

                // Floating platform
                case 'l':
                    return LoadTile("Pattern2", TileCollision.Platform);

                // Floating platform
                case '7':
                    return LoadTile("Steel1", TileCollision.Impassable);

                // Floating platform
                case 'n':
                    return LoadTile("Steel2", TileCollision.Impassable);

                // Floating platform
                case 'o':
                    return LoadTile("Steel3", TileCollision.Impassable);

                // Floating platform
                case '6':
                    return LoadTile("Steel4", TileCollision.Impassable);

                // Floating platform
                case 'q':
                    return LoadTile("Steel5", TileCollision.Impassable);

                // Floating platform
                case 'r':
                    return LoadTile("Steel6", TileCollision.Impassable);

                // Floating platform
                case 's':
                    return LoadTile("Steel7", TileCollision.Impassable);

                // Floating platform
                case 't':
                    return LoadTile("Stone1", TileCollision.Impassable);

                // Floating platform
                case 'u':
                    return LoadTile("Stone2", TileCollision.Impassable);

                // Floating platform
                case 'v':
                    return LoadTile("Stone3", TileCollision.Impassable);

                // Floating platform
                case 'w':
                    return LoadTile("Stone4", TileCollision.Impassable);

                // Floating platform
                case '9':
                    return LoadTile("Stone5", TileCollision.Impassable);

                // Floating platform
                case 'y':
                    return LoadTile("Stone6", TileCollision.Impassable);


                // Unknown tile type character
                default:
                    throw new NotSupportedException(String.Format("Unsupported tile type character '{0}' at position {1}, {2}.", tileType, x, y));
            }
        }

        /// <summary>
        /// Creates a new tile. The other tile loading methods typically chain to this
        /// method after performing their special logic.
        /// </summary>
        /// <param name="name">
        /// Path to a tile texture relative to the Content/Tiles directory.
        /// </param>
        /// <param name="collision">
        /// The tile collision type for the new tile.
        /// </param>
        /// <returns>The new tile.</returns>
        private Tile LoadTile(string name, TileCollision collision)
        {
            return new Tile(Content.Load<Texture2D>("Tiles/" + name), collision);
        }

        // 3/29/2012 - moving platforms
        private Tile LoadMovableTile(int x, int y, TileCollision collision)
        {
            Point position = GetBounds(x, y).Center;
            movableTiles.Add(new MovableTile(this, new Vector2(position.X, position.Y), collision));
            return new Tile(null, TileCollision.Passable);
        }

        /// <summary>
        /// Loads a tile with a random appearance.
        /// </summary>
        /// <param name="baseName">
        /// The content name prefix for this group of tile variations. Tile groups are
        /// name LikeThis0.png and LikeThis1.png and LikeThis2.png.
        /// </param>
        /// <param name="variationCount">
        /// The number of variations in this group.
        /// </param>
        private Tile LoadVarietyTile(string baseName, int variationCount, TileCollision collision)
        {
            int index = random.Next(variationCount);
            return LoadTile(baseName + index, collision);
        }


        /// <summary>
        /// Instantiates a player, puts him in the level, and remembers where to put him when he is resurrected.
        /// </summary>
        private Tile LoadStartTile(int x, int y)
        {
            if (Player != null)
                throw new NotSupportedException("A level may only have one starting point.");

            start = RectangleExtensions.GetBottomCenter(GetBounds(x, y));
            player = new Player(this, start);

            return new Tile(null, TileCollision.Passable);
        }

        /// <summary>
        /// Remembers the location of the level's exit.
        /// </summary>
        private Tile LoadExitTile(int x, int y)
        {
            if (exit != InvalidPosition)
                throw new NotSupportedException("A level may only have one exit.");

            exit = GetBounds(x, y).Center;

            return LoadTile("Exit", TileCollision.Passable);
        }



        /// <summary>
        /// Instantiates an enemy and puts him in the level.
        /// </summary>
        private Tile LoadEnemyTile(int x, int y, string spriteSet)
        {
            Vector2 position = RectangleExtensions.GetBottomCenter(GetBounds(x, y));
            enemies.Add(new Enemy(this, position, spriteSet));

            return new Tile(null, TileCollision.Passable);
        }

        /// <summary>
        /// Instantiates a gem and puts it in the level.
        /// </summary>
        private Tile LoadGemTile(int x, int y)
        {
            Point position = GetBounds(x, y).Center;
            gems.Add(new Gem(this, new Vector2(position.X, position.Y)));

            return new Tile(null, TileCollision.Passable);
        }

        /// <summary>
        /// Unloads the level content.
        /// </summary>
        public void Dispose()
        {
            Content.Unload();
        }

        #endregion

        #region Bounds and collision

        /// <summary>
        /// Gets the collision mode of the tile at a particular location.
        /// This method handles tiles outside of the levels boundries by making it
        /// impossible to escape past the left or right edges, but allowing things
        /// to jump beyond the top of the level and fall off the bottom.
        /// </summary>
        public TileCollision GetCollision(int x, int y)
        {
            // Prevent escaping past the level ends.
            if (x < 0 || x >= Width)
                return TileCollision.Impassable;
            // Allow jumping past the level top and falling through the bottom.
            if (y < 0 || y >= Height)
                return TileCollision.Passable;

            return tiles[x, y].Collision;
        }

        /// <summary>
        /// Gets the bounding rectangle of a tile in world space.
        /// </summary>        
        public Rectangle GetBounds(int x, int y)
        {
            return new Rectangle(x * Tile.Width, y * Tile.Height, Tile.Width, Tile.Height);
        }

        /// <summary>
        /// Width of level measured in tiles.
        /// </summary>
        public int Width
        {
            get { return tiles.GetLength(0); }
        }

        /// <summary>
        /// Height of the level measured in tiles.
        /// </summary>
        public int Height
        {
            get { return tiles.GetLength(1); }
        }

        #endregion

        #region Update

        /// <summary>
        /// Updates all objects in the world, performs collision between them,
        /// and handles the time limit with scoring.
        /// </summary>
        public void Update(
            GameTime gameTime,
            KeyboardState keyboardState,
            GamePadState gamePadState,
            TouchCollection touchState,
            AccelerometerState accelState)
        {
            // Pause while the player is dead or time is expired.
            if (!Player.IsAlive || TimeRemaining == TimeSpan.Zero)
            {
                // Still want to perform physics on the player.
                Player.ApplyPhysics(gameTime);
            }
            else if (ReachedExit)
            {
                // Animate the time being converted into points.
                int seconds = (int)Math.Round(gameTime.ElapsedGameTime.TotalSeconds * 100.0f);
                seconds = Math.Min(seconds, (int)Math.Ceiling(TimeRemaining.TotalSeconds));
                timeRemaining -= TimeSpan.FromSeconds(seconds);
                score += seconds * PointsPerSecond;
            }
            else
            {
                timeRemaining -= gameTime.ElapsedGameTime;
                Player.Update(gameTime, keyboardState, gamePadState, touchState, accelState);
                UpdateGems(gameTime);

                // Falling off the bottom of the level kills the player.
                if (Player.BoundingRectangle.Top >= Height * Tile.Height)
                    OnPlayerKilled(null);

                UpdateEnemies(gameTime);

                // 3/29/2012 - moving tiles
                UpdateMovableTiles(gameTime);

                // The player has reached the exit if they are standing on the ground and
                // his bounding rectangle contains the center of the exit tile. They can only
                // exit when they have collected all of the gems.
                if (Player.IsAlive &&
                    Player.IsOnGround &&
                    Player.BoundingRectangle.Contains(exit))
                {
                    OnExitReached();
                }
            }

            // Clamp the time remaining at zero.
            if (timeRemaining < TimeSpan.Zero)
                timeRemaining = TimeSpan.Zero;
        }

        // 3/30/2012 - moving tiles
        private void UpdateMovableTiles(GameTime gameTime)
        {
            foreach (MovableTile tile in movableTiles)
            {
                tile.Update(gameTime);
                if (tile.PlayerIsOn)
                {
                    //Make player move with tile if the player is on top of tile
                    player.Position += tile.Velocity;
                }
            }
        }

        /// <summary>
        /// Animates each gem and checks to allows the player to collect them.
        /// </summary>
        private void UpdateGems(GameTime gameTime)
        {
            for (int i = 0; i < gems.Count; ++i)
            {
                Gem gem = gems[i];

                gem.Update(gameTime);

                if (gem.BoundingCircle.Intersects(Player.BoundingRectangle))
                {
                    gems.RemoveAt(i--);
                    OnGemCollected(gem, Player);
                }
            }
        }

        /// <summary>
        /// Animates each enemy and allow them to kill the player.
        /// </summary>
        private void UpdateEnemies(GameTime gameTime)
        {
            foreach (Enemy enemy in enemies)
            {
                if (enemy.Alive) //checking for a dead enemy (so they don't keep moving)
                {
                    enemy.Update(gameTime);

                    // Touching an enemy instantly kills the player
                    if (enemy.BoundingRectangle.Intersects(Player.BoundingRectangle))
                    {
                        OnPlayerKilled(enemy);
                    }
                }//end check for dead enemy
            }
        }

        /// <summary>
        /// Called when a gem is collected.
        /// </summary>
        /// <param name="gem">The gem that was collected.</param>
        /// <param name="collectedBy">The player who collected this gem.</param>
        private void OnGemCollected(Gem gem, Player collectedBy)
        {
            score += Gem.PointValue;

            gem.OnCollected(collectedBy);
        }

        /// <summary>
        /// Called when the player is killed.
        /// </summary>
        /// <param name="killedBy">
        /// The enemy who killed the player. This is null if the player was not killed by an
        /// enemy, such as when a player falls into a hole.
        /// </param>
        private void OnPlayerKilled(Enemy killedBy)
        {
            Player.OnKilled(killedBy);
        }

        /// <summary>
        /// Called when the player reaches the level's exit.
        /// </summary>
        private void OnExitReached()
        {

            Player.OnReachedExit();
            var settings = IsolatedStorageSettings.ApplicationSettings;

            if (settings.Contains("SoundEffectsSetting"))
            {

                //Retrieve data stored in the settings
                string determineFxSettingAfterRelaunch = settings["SoundEffectsSetting"].ToString();

                if (determineFxSettingAfterRelaunch == "false")
                {
                  //  soundFxCanPlay = false;
                }
                else
                {
                    exitReachedSound.Play();
                }
            }
            else //if the isolated storage setting hasn't been created yet, then create it and set it to "off" to turn off music.
            {
                //should only hit this the first time the app is run
                exitReachedSound.Play();
            }
            
            reachedExit = true;
        }


        /// <summary>
        /// Restores the player to the starting point to try the level again.
        /// </summary>
        public void StartNewLife()
        {
            //checkpoint stuff
            //After we die, this is called. If there IS a checkpoint saved,
            //we start there. Otherwise we begin at the start point.
            if (checkpoint != Vector2.Zero)
                Player.Reset(checkpoint);
            else
                Player.Reset(start); //used to be the only thing in this method

        }

        #endregion

        #region Draw

        /// <summary>
        /// Draw everything in the level from background to foreground.
        /// </summary>
        ////public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        ////{
        ////    for (int i = 0; i <= EntityLayer; ++i)
        ////        spriteBatch.Draw(layers[i], Vector2.Zero, Color.White);

        ////    DrawTiles(spriteBatch);

        ////    foreach (Gem gem in gems)
        ////        gem.Draw(gameTime, spriteBatch);

        ////    Player.Draw(gameTime, spriteBatch);

        ////    foreach (Enemy enemy in enemies)
        ////        enemy.Draw(gameTime, spriteBatch);

        ////    for (int i = EntityLayer + 1; i < layers.Length; ++i)
        ////        spriteBatch.Draw(layers[i], Vector2.Zero, Color.White);
        ////}

        public void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            spriteBatch.Begin();
            for (int i = 0; i <= EntityLayer; ++i)
                layers[i].Draw(spriteBatch, cameraPosition);
            spriteBatch.End();

            ScrollCamera(spriteBatch.GraphicsDevice.Viewport);
            //Matrix cameraTransform = Matrix.CreateTranslation(-cameraPosition, 0.0f, 0.0f); //3-23-2012 vertical scrolling
            Matrix cameraTransform = Matrix.CreateTranslation(-cameraPosition, -cameraPositionYAxis, 0.0f);
            // spriteBatch.Begin(BlendState.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None, cameraTransform);
            //spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, cameraTransform);
            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullCounterClockwise, null, cameraTransform);
            // SaveStateMode.None
            DrawTiles(spriteBatch);

            // 3/30/2012 - moving tiles :)
            foreach (MovableTile tile in movableTiles)
                tile.Draw(gameTime, spriteBatch);


            foreach (Gem gem in gems)
                gem.Draw(gameTime, spriteBatch);

            Player.Draw(gameTime, spriteBatch);

            foreach (Enemy enemy in enemies)
                enemy.Draw(gameTime, spriteBatch);

            spriteBatch.End();

            spriteBatch.Begin();
            for (int i = EntityLayer + 1; i < layers.Length; ++i)
                layers[i].Draw(spriteBatch, cameraPosition);
            spriteBatch.End();
        }


        private void ScrollCamera(Viewport viewport)
        {
#if ZUNE
const float ViewMargin = 0.45f;
#else
            const float ViewMargin = 0.35f;
#endif
            //x axis
            // Calculate the edges of the screen.
            float marginWidth = viewport.Width * ViewMargin;
            float marginLeft = cameraPosition + marginWidth;
            float marginRight = cameraPosition + viewport.Width - marginWidth;

            // Calculate how far to scroll when the player is near the edges of the screen.
            float cameraMovement = 0.0f;
            if (Player.Position.X < marginLeft)
                cameraMovement = Player.Position.X - marginLeft;
            else if (Player.Position.X > marginRight)
                cameraMovement = Player.Position.X - marginRight;



            //y axis
            // Calculate the scrolling borders for the Y-axis. 
            const float TopMargin = 0.3f;
            const float BottomMargin = 0.1f;
            float marginTop = cameraPositionYAxis + viewport.Height * TopMargin;
            float marginBottom = cameraPositionYAxis + viewport.Height - viewport.Height * BottomMargin;

            // Calculate how far to vertically scroll when the player is near the top or bottom of the screen. 
            float cameraMovementY = 0.0f;
            if (Player.Position.Y < marginTop) //above the top margin 
                cameraMovementY = Player.Position.Y - marginTop;
            else if (Player.Position.Y > marginBottom) //below the bottom margin 
                cameraMovementY = Player.Position.Y - marginBottom;

            //new variable (called maxCameraPositionYOffset) that tracks the highest scrolling point for the camera:
            float maxCameraPositionYOffset = Tile.Height * Height - viewport.Height;

            //not sure here
            cameraPositionYAxis = MathHelper.Clamp(cameraPositionYAxis + cameraMovementY, 0.0f, maxCameraPositionYOffset);


            // Update the camera position, but prevent scrolling off the ends of the level.
            float maxCameraPosition = Tile.Width * Width - viewport.Width;
            cameraPosition = MathHelper.Clamp(cameraPosition + cameraMovement, 0.0f, maxCameraPosition);
        }


        /// <summary>
        /// Draws each tile in the level.
        /// </summary>
        private void DrawTiles(SpriteBatch spriteBatch)
        {

            // Calculate the visible range of tiles.
            int left = (int)Math.Floor(cameraPosition / Tile.Width);
            int right = left + spriteBatch.GraphicsDevice.Viewport.Width / Tile.Width;
            right = Math.Min(right, Width - 1);


            // For each tile position
            for (int y = 0; y < Height; ++y)
            {
                //for (int x = 0; x < Width; ++x)
                for (int x = left; x <= right; ++x)
                {
                    // If there is a visible tile in that position
                    Texture2D texture = tiles[x, y].Texture;
                    if (texture != null)
                    {
                        // Draw it in screen space.
                        Vector2 position = new Vector2(x, y) * Tile.Size;
                        spriteBatch.Draw(texture, position, Color.White);
                    }
                }
            }
        }

        #endregion
    }
}
